package dirty

import (
	"math"
	"strconv"
	"strings"
)

type Int int

func (Int) isElement() {}
func (Int) getType() ElementType {
	return ElemInt
}
func (i Int) String() string {
	return strconv.FormatInt(int64(i), 10)
}

type Float float64

func (Float) isElement() {}
func (Float) getType() ElementType {
	return ElemFloat
}
func (i Float) String() string {
	return strconv.FormatFloat(float64(i), 'f', -1, 64)
}

func convertDozenal(d string) string {
	result := ""
	for _, c := range d {
		if c == '↊' {
			result += "a"
		} else if c == '↋' {
			result += "b"
		} else if c == '·' {
			result += "."
		} else {
			result += string(c)
		}
	}
	return result
}

func parseNumber(t token) (token, error) {
	l := len(t.t)
	if (in('.', []rune(t.t)) && t.t[l-1] != '.') || (in('·', []rune(t.t)) && t.t[l-1] != '·') {
		return parseFloat(t)
	} else if in('e', []rune(t.t)) && t.t[0] != '0' {
		return parseEngFloat(t)
	} else {
		return parseInt(t)
	}
}

func parseFloat(t token) (token, error) {
	t.ttype = FLOAT
	if in('·', []rune(t.t)) {
		number := strings.SplitN(convertDozenal(t.t), ".", 2)
		whole, err := strconv.ParseInt(number[0], 12, 64)
		if err != nil {
			return token{}, err
		}
		fraction, err := strconv.ParseUint(number[1], 12, 64)
		if err != nil {
			return token{}, err
		}

		t.t = ""
		w := float64(whole)
		if fraction != 0 {
			var sign float64
			if w < 0 {
				sign = -1.0
			} else {
				sign = 1.0
			}
			l := math.Floor(math.Log10((float64(fraction))/math.Log10(12)) + 1)
			w += (float64(fraction) / math.Pow(12, l)) * sign
		}
		t.f = &w
		return t, nil
	} else {
		f, err := strconv.ParseFloat(t.t, 64)
		t.t = ""
		t.f = &f
		return t, err
	}
}

func parseEngFloat(t token) (token, error) {
	t.ttype = FLOAT
	number := strings.SplitN(t.t, "e", 2)
	a, err := strconv.ParseFloat(number[0], 64)
	if err != nil {
		return token{}, err
	}
	b, err := strconv.ParseInt(number[1], 10, 64)
	if err != nil {
		return token{}, err
	}
	num := a * math.Pow10(int(b))
	t.t = ""
	t.f = &num
	return t, nil
}

func parseComma(num string, z int) (string, error) {
	num2 := ""
	prevIsComma := false
	lastComma := -1
	zeroes := ""
	for i := 0; i < z; i++ {
		zeroes += "0"
	}
	for i, d := range num {
		if d == ',' {
			if prevIsComma {
				num2 += zeroes
			} else if !((i-lastComma) == z+1 || lastComma == -1) {
				return "", NewCommaError(num)
			}
			lastComma = i
		} else {
			num2 += string(d)
		}
		prevIsComma = d == ','
	}
	return num2, nil
}

func parseInt(t token) (token, error) {
	num2 := t.t
	base := 10

	if t.t[0] == '0' && t.t[1] == 'b' {
		num := t.t[2:]
		if num[0] == ',' {
			num = "1" + num
		}

		var err error = nil
		num2, err = parseComma(num, 8)
		if err != nil {
			return token{}, err
		}

		num2 = "0b" + num2
		base = 0
	}
	if t.t[0] == '0' && t.t[1] == 'o' {
		num2 = t.t
		base = 0
	}
	if t.t[0] == '0' && t.t[1] == 'x' {
		num2 = t.t
		base = 0
	}
	if in('·', []rune(t.t)) {
		num, err := parseComma(t.t, 3) // 4?
		if err != nil {
			return token{}, err
		}
		num2 = convertDozenal(num)
		num2 = num2[:len(num2)-1]
		base = 12
	}
	if in('.', []rune(t.t)) {
		num2 = t.t[:len(t.t)-1]
		var err error = nil
		parseComma(num2, 3)
		if err != nil {
			return token{}, err
		}
		base = 10
	}
	result, err := strconv.ParseInt(num2, base, 64)
	t.i = &result
	return t, err
}
